package sketch.specs;

import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ArrayCreation;
import org.eclipse.jdt.core.dom.ArrayInitializer;
import org.eclipse.jdt.core.dom.ArrayType;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.PrimitiveType;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;

import sketch.ast.ASTUtils;
import sketch.generator.ValueGenerator;
import sketch.generator.experiment.ValueGroupGenerator;
import sketch.specs.StepwiseGeneratorGroupGenerator;
import sketch.util.Checker;

public abstract class SketchedBlock {
	
	/**
	 * A common block which may contain lists of declared value choices
	 * */
	protected final Block realBlock;
	
	//variables declared in this level!
	protected final List<VariableDeclarationStatement> variable_declarations;
	protected final List<SketchedBlock> childrenBlocks = new LinkedList<SketchedBlock>();
	protected final List<SketchedBlock> allSubBlocks = new LinkedList<SketchedBlock>();
	
	/**
	 * The parent sketched block and the method declaration which it belongs to.
	 * These two fields could only be set once!
	 * */
	protected SketchedBlock parentBlock = null;
	protected SketchedMethodDeclaration methodDeclaration = null;
	private boolean isSetted = false;

	public SketchedBlock(Block realBlock) {
		Checker.checkNull(realBlock, "The realBlock input could not be null!");
		//find all value declaration statements
		SpecValueDeclarationFinder finder = new SpecValueDeclarationFinder(realBlock);
		this.variable_declarations = finder.getAllValueDeclaredStatements();
		//System.out.println(this.variable_declarations);
		this.realBlock = realBlock;
	}
	
//	private void resetRealBlock(Block block) {
//		Checker.checkTrue(block.getAST() == this.realBlock.getAST(), "The ast trees are not equal!");
//		Checker.checkTrue(realBlock.getStartPosition() == block.getStartPosition(), "the start position are not equal.");
//		Checker.checkTrue(block.getLength() == this.realBlock.getLength(), "The block length are not equal.");
//		this.realBlock = block;
//	}
	
	public Block getRealBlock() {
		return this.realBlock;
	}
	
	public void setSketchedBlockAndMethod(SketchedBlock parentBlock, SketchedMethodDeclaration methodDeclaration) {
		Checker.checkTrue(!this.isSetted, "The sketched block has already been setted!");
		Checker.checkNull(methodDeclaration, "The method declaration input could not be null!");
		this.parentBlock = parentBlock; //this could be null, for the top levels
		this.methodDeclaration = methodDeclaration;
		this.isSetted = true; //has set
	}
	
	boolean isParentSetted() {
		return this.isSetted;
	}
	
	public int getStartPosition() {
		return realBlock.getStartPosition();
	}
	
	public int getEndPosition() {
		return this.getStartPosition() + this.realBlock.getLength();
	}
	
	public List<Block> expandAll() {
		List<Block> expandResults = new LinkedList<Block>();
		//XXX this is very error-prone, need to keep consistence with both the if/else branches
		if(this.childrenBlocks.isEmpty()) {
//			expandResults.add(this.realBlock);
			expandResults.addAll(this.replicate());
			return this.expandWithValues(expandResults, this.variable_declarations);
		} else {
			Block clonedRealBlock = ASTUtils.deepClone(this.realBlock);
			expandResults.add(clonedRealBlock);
			//expandResults.addAll(this.replicate());
			//go through each child block
			//FIXME did not call self.replication!
			for(SketchedBlock child : this.childrenBlocks) {
				List<Block> fringe = new LinkedList<Block>();
				List<Block> childBlocks = child.expandAll();
				//clone a copy
				Block childRealBlock = ASTUtils.deepClone(child.realBlock);
				//for each block in all, we need to replace that one with multiple
				for(Block wholeBlock : expandResults) {
					wholeBlock = ASTUtils.deepClone(wholeBlock);
					//replace the (childRealBlock) with childBlocks
					for(Block newChildBlock : childBlocks) {
						//replace it, and add to all
						SourceExpansionVisitor visitor = new SourceExpansionVisitor(wholeBlock, childRealBlock, newChildBlock);
						Block newReplacedBlock = visitor.getReplacedBlock();
						fringe.add(newReplacedBlock);
					}
				}
				//next round of iteration, first need to clear the expand results
				expandResults.clear();
				expandResults.addAll(fringe);
			}
		}
		//the normal return without calling this.replicate()
//		return expandResults;
		//keep a copy
		List<Statement> realBlockStatements = this.realBlock.statements();
		
		List<Block> retBlocks = new LinkedList<Block>();
		for(Block expandResult : expandResults) {
			this.realBlock.statements().clear();
			List<Statement> statements = expandResult.statements();
			for(Statement s : statements) {
				this.realBlock.statements().add(ASTUtils.deepClone(s));
			}
			List<Block> replicated_results = this.replicate();
			retBlocks.addAll(replicated_results);
		}
		
		//recover it
		this.realBlock.statements().clear();
		this.realBlock.statements().addAll(realBlockStatements);
		
		List<Block> textualUniqueBlocks = ASTUtils.removeTextualRedanduance(retBlocks);
		
		List<Block> blocksAfterValueExpansion = expandWithValues(textualUniqueBlocks, this.variable_declarations);
		
		List<Block> blocksAfterExpandingIdBlocks = IdentifierBlockExpander.expandOnIdentifier(this.realBlock,
				this.childrenBlocks, blocksAfterValueExpansion); 
			//expandOnIdentifierBlocks(blocksAfterValueExpansion);
		
		//return blocksAfterValueExpansion;
        return blocksAfterExpandingIdBlocks;
	}
	
	static List<Block> expandWithValues(List<Block> blocks, List<VariableDeclarationStatement> variable_declarations) {
		//System.out.println(blocks);
		if(variable_declarations.isEmpty()) {
			return blocks;
		} else {
			List<Block> expandedBlocks = new LinkedList<Block>();
			for(Block block : blocks) {
				expandedBlocks.addAll(expandWithValuesForSingleBlock(block, variable_declarations));
			}
			return expandedBlocks;
		}
	}
	
	//duplicate as in SketchedRepeatBlock
	/**
	 * When expanding variables in a single block, we need to enumerate all possible combinations, here is an example:
	 * int x = Values.choose(1, 2);
	 * int y = Values.choose(3, 4);
	 * foo(x,y);
	 * 
	 * will be expanded into f(1,3), foo(1, 4), foo(2, 3), foo(2, 4), that is totally 4 duplications
	 * 
	 * another example will be:
	 * int x = Values.choose(1, 2);
	 * foo(x);
	 * int y = Values.choose(3, 4);
	 * bar(y);
	 * 
	 * will be expanded into:  foo(1), bar(3)   ;  foo(1), bar(4);  foo(2), bar(3);  foo(2), bar(4)
	 * */
	static List<Block> expandWithValuesForSingleBlock(Block block, List<VariableDeclarationStatement> variable_declarations) {
		List<Block> retBlocks = new LinkedList<Block>();
		
		Map<SimpleName, VariableDeclarationFragment> varMap = ASTUtils.extractDefinedVariableMapping(variable_declarations);
		Map<SimpleName, Type> typemaps = ASTUtils.extractDefinedVariableTypeMapping(variable_declarations);
		Set<SimpleName> vars = varMap.keySet();
		if(vars.isEmpty()) {
			retBlocks.add(ASTUtils.deepClone(block));
			return retBlocks;
		}
		//has some vars
		UsedVariableVisitor usedVarVisitor = new UsedVariableVisitor(vars);
		usedVarVisitor.visit(block);
		Collection<SimpleName> usedVars = usedVarVisitor.getUsedVariables();
		if(usedVars.isEmpty()) {
			retBlocks.add(ASTUtils.deepClone(block));
			return retBlocks;
		}
		
		//has some used place
		Map<SimpleName, List<SimpleName>> dumap = usedVarVisitor.getDUMap();
		//we need to create generator for each simpleName declaration
//		Map<SimpleName,  ValueGenerator<Expression>> nameGeneratorMap =
//			new LinkedHashMap<SimpleName,  ValueGenerator<Expression>>();
		Map<SimpleName, ValueGroupGenerator<Expression>> nameValueGroupGeneratorMap =
			new LinkedHashMap<SimpleName, ValueGroupGenerator<Expression>>();
		for(SimpleName simpleName : dumap.keySet()) {
			DeclaredValueExtractor extractor = new DeclaredValueExtractor(varMap.get(simpleName));
			//nameGeneratorMap.put(simpleName, extractor.getArgumentValueGenerator());
			nameValueGroupGeneratorMap.put(simpleName, extractor.getArgumentGroupValueGenerator());
		}
		//xxx
//		StepwiseGeneratorGroupGenerator<Expression> group = new StepwiseGeneratorGroupGenerator<Expression>(nameGeneratorMap, typemaps);
		StepExhaustedGroupGenerator<Expression> group = new StepExhaustedGroupGenerator<Expression>(nameValueGroupGeneratorMap, typemaps);
		
		while(group.hasNext()) {
			//System.out.println("  --- group hasnext: ");
			group.next();
			Block replicate_block = ASTUtils.deepClone(block);
			//Map<SimpleName, String> nameMap = new LinkedHashMap<SimpleName, String>();
			Map<SimpleName, Expression> expressionMap = new LinkedHashMap<SimpleName, Expression>();
			for(SimpleName name : nameValueGroupGeneratorMap.keySet()) {
				Object obj = group.lookupCurrentValue(name);
				
				Expression expr = null;
				if(obj instanceof Expression) {
					expr = (Expression)obj;
				} else if (obj instanceof List) {
					List<Expression> expressionList = (List<Expression>)obj;
					AST ast = name.getAST();
					Type t = ASTUtils.deepClone(typemaps.get(name));
					Checker.checkTrue(t instanceof ArrayType, "t should be an array type, but now it is: " + t.getClass());
					ArrayType at = (ArrayType)t;
					t.delete();
					ArrayCreation ac3 = ast.newArrayCreation();
					 ac3.setType(
					    //ast.newArrayType(
					    		at
					       //ast.newSimpleType(ast.newSimpleName("Integer"))
					       );
//					       ast.newPrimitiveType(PrimitiveType.INT)));
					 ArrayInitializer ai = ast.newArrayInitializer();
					 ac3.setInitializer(ai);
					 for(Expression e : expressionList) {
					     ai.expressions().add(ast.newNumberLiteral(e.toString()));
					 }
					//init the expression
				   expr = ac3;
				   //System.out.println(expr);
					
				} else {
					throw new RuntimeException("Unexpected type: " + obj.getClass());
				}
				Checker.checkNull(expr, "The expression could not be  null!");
				expressionMap.put(name, expr);
			}
			
			//using another strategy is to introduce variable declarations
			//this can be removed, start here
			int count = 0;
			for(SimpleName name : expressionMap.keySet()) {
				Type t = ASTUtils.deepClone(typemaps.get(name));
				t.delete();
				VariableDeclarationFragment fragment = replicate_block.getAST().newVariableDeclarationFragment();
				SimpleName new_decl_name = replicate_block.getAST().newSimpleName(name.toString() + "_" + (count++));
				fragment.setName(new_decl_name);
				Expression init = ASTUtils.deepClone(expressionMap.get(name));
				init.delete();
				fragment.setInitializer(init);
				VariableDeclarationStatement newvardeclstmt = replicate_block.getAST().newVariableDeclarationStatement(fragment);
				newvardeclstmt.setType(t);
				//add to the block
				replicate_block.statements().add(0, newvardeclstmt);
				//System.out.println("replicate_block inside: " + replicate_block);
				//update the expression map
				//expressionMap.put(name, new_decl_name);
				//expressionMap.remove(name);
				expressionMap.put(name, new_decl_name);
			}
			//end here
			
			//System.out.println("replicate_block: " + replicate_block);
			//replace the name
			//System.out.println(expressionMap);
			SimpleNameReplaceVisitor visitor = new SimpleNameReplaceVisitor(expressionMap);
			//here do the replacement
			replicate_block.accept(visitor);
			
			//System.out.println(replicate_block);
			
			retBlocks.add(replicate_block);
		}
		
		//System.out.println(retBlocks);
		return retBlocks;
	}
	
	
	@Override
	public String toString() {
		return this.toCodeString();
	}
	
	int numOfSubBlocks() {
		return this.allSubBlocks.size();
	}
	
	public int numOfChildren() {
		return this.childrenBlocks.size();
	}
	
	void addSubBlocks(Collection<SketchedBlock> blocks) {
		for(SketchedBlock block : blocks) {
			this.addSubBlock(block);
		}
	}
	
	void addSubBlock(SketchedBlock block) {
		this.allSubBlocks.add(block);
	}
	
	void buildSubBlockHierarchy() {
		if(this.allSubBlocks.isEmpty()) {
			return;
		}
		
		//the rough algorithm psedo code
		//find the top level blocks, and their sub-blocks
		List<SketchedBlock> firstLevel = SketchedBlock.findFirstLevelBlocks(allSubBlocks);
		//add to the childrenBlocks
		this.childrenBlocks.addAll(firstLevel);
		//the remaining blocks
		List<SketchedBlock> remainings = new LinkedList<SketchedBlock>();
		for(SketchedBlock b : allSubBlocks) {
			if(!firstLevel.contains(b)) {
				remainings.add(b);
			}
		}
		Checker.checkTrue((remainings.size() + this.childrenBlocks.size())== allSubBlocks.size(),
				"The number of remaining is: " + remainings.size() + ", the number of children is: "
				+ this.childrenBlocks.size() + ", the number of all sub blocks: " + allSubBlocks.size());
//		if(remainings.isEmpty()) {
//			return;
//		}
		int count = 0; //for correctness checking
		for(SketchedBlock child : this.childrenBlocks) {
			List<SketchedBlock> subBlocks = SketchedBlock.findSubBlocks(child, remainings);
			count = count + subBlocks.size();
			child.addSubBlocks(subBlocks);
		}
		Checker.checkTrue(count == remainings.size(), "The count: " + count + " does not equal to " +
				"the remaining block size: " + remainings.size());
		//call build hierarchical in the sub blocks
		for(SketchedBlock child : this.childrenBlocks) {
			child.buildSubBlockHierarchy();
		}
	}
	
	//the method each class must override, like macro expansion
//	protected abstract List<SketchedBlock> expand();
	
	protected abstract List<Block> replicate();
	
	protected abstract SketchedBlock newInstance(Block block);
	
	protected String toCodeString() {
		return this.realBlock.toString();
	}
	
	/**
	 * Publicly accessible static methods
	 * */
	public static List<SketchedBlock> findFirstLevelBlocks(List<SketchedBlock> flatList) {
		List<SketchedBlock> firstLevel = new LinkedList<SketchedBlock>();
		if(!flatList.isEmpty()) {
			if(flatList.size() == 1) {
				firstLevel.addAll(flatList);
			} else {
				//pick up all the first level elements here
				Set<SketchedBlock> topLevel = new LinkedHashSet<SketchedBlock>();
				for(SketchedBlock block : flatList) {
					List<SketchedBlock> needToRemove = new LinkedList<SketchedBlock>();
					boolean shouldInsert = true;
					for(SketchedBlock alreadyInBlock : topLevel) {
						if(ASTUtils.isNested(alreadyInBlock.realBlock, block.realBlock)) {
							//the already in block is a sub block of this
							needToRemove.add(alreadyInBlock);
						} else if (ASTUtils.isNested(block.realBlock, alreadyInBlock.realBlock)){
							shouldInsert = false;
						}
					}
					topLevel.removeAll(needToRemove);
					if(shouldInsert) {
						topLevel.add(block);
					}
				}
				firstLevel.addAll(topLevel);
			}
		}
		return firstLevel;
	}
	
	public static List<SketchedBlock> findSubBlocks(SketchedBlock block, List<SketchedBlock> list) {
		List<SketchedBlock> subBlocks = new LinkedList<SketchedBlock>();
		for(SketchedBlock b : list) {
			if(block == b) {
				continue;
			}
			if(ASTUtils.isNested(b.realBlock, block.realBlock)) {
				subBlocks.add(b);
			}
		}
		return subBlocks;
	}
}